//typedef unsigned char byte;
//typedef unsigned char boolean;

#include <WProgram.h>
#include <limits.h>
#include <ADB.h>

// ADB stuff
#define ADB_CMD_SEPS "\n"
#define ADB_TOKEN_SEPS " \t"

// ADB CMD PROCESSING
static char* adbBufP = 0;

static char* getADBcmd();
static void processADBmessage(char* cmd);
char* getNextToken(char** buf);
bool decodePin(char* pinS, uint8_t* pin);
void bmsCmd(char* buf, uint16_t len);

extern boolean setthr(uint16_t uvSet, uint16_t uvClr, uint16_t ovSet, uint16_t ovClr);
extern void setDisplayFreqMs(uint32_t);
extern void gotINITCommand();

/*
 * MAIN CONTROLLER COMMANDS
 * 
 */
// form: e.g. <pc> A5 o
void pinConfig(char* buf)
{
  char* pinS = getNextToken(&buf);
  uint8_t pin;
  if (!decodePin(pinS,&pin)) {
      return;
  }

  char* modeS = getNextToken(&buf);
  if (!modeS || strlen(modeS) != 1) {
    Serial.println("ADB cmd mode!");
    if (!modeS) Serial.println("null mode");
    else Serial.println(modeS);
    return;
  }
  bool outputMode;
  if (modeS[0]=='o' || modeS[0]=='O') {
    outputMode = true;
  } else if (modeS[0]=='i' || modeS[0]=='I') {
    outputMode = false;
  } else {
    Serial.println("ADB cmd mode2!");
    return;
  }

  pinMode(pin, outputMode?OUTPUT:INPUT);
}

// form: e.g. <ph> A5
void pinHigh(char* buf)
{
  uint8_t pin;
  char* pinS = getNextToken(&buf);
  if (decodePin(pinS,&pin)) {
    digitalWrite(pin, HIGH);
  }
}

// form: e.g. <pl> A5
void pinLow(char* buf)
{
  uint8_t pin;
  char* pinS = getNextToken(&buf);
  if (decodePin(pinS,&pin)) {
    digitalWrite(pin, LOW);
  }
}

/*
 * BMS COMMANDS
 */
// process init command. I2C init is triggered. At the end
// we generate a "<count> <# cells>" message which acts as the reply.
void doInit()
{
  Serial.println("got <init>");
  gotINITCommand();
}

// Configure thresholds 
//  cmd: '<tc>' <UVset> <UVclear> <OVset> <OVclear>
void thresholdConfig(char* buf)
{
  uint16_t uvSet, uvClr, ovSet, ovClr;
  char* val = getNextToken(&buf);
  if (!val || !strlen(val)) {
    Serial.println("thresholdConfig!");
    return;
  }
  uvSet = strtoul(val, (char **)NULL, 10);
  val = getNextToken(&buf);
  if (!val || !strlen(val)) {
    Serial.println("thresholdConfig!");
    return;
  }
  uvClr = strtoul(val, (char **)NULL, 10);
  val = getNextToken(&buf);
  if (!val || !strlen(val)) {
    Serial.println("thresholdConfig!");
    return;
  }
  ovSet = strtoul(val, (char **)NULL, 10);
  val = getNextToken(&buf);
  if (!val || !strlen(val)) {
    Serial.println("thresholdConfig!");
    return;
  }
  ovClr = strtoul(val, (char **)NULL, 10);
  if (uvSet == ULONG_MAX || !uvSet ||
      uvClr == ULONG_MAX || !uvClr ||
      ovSet == ULONG_MAX || !ovSet ||
      ovClr == ULONG_MAX || !ovClr) {
    Serial.println("thresholdConfig vals!");
    return;
  }
  Serial.print("got <tc> "); 
  Serial.print(uvSet); Serial.print(" ");
  Serial.print(uvClr); Serial.print(" ");
  Serial.print(ovSet); Serial.print(" ");
  Serial.print(ovClr); Serial.print(" ");
  Serial.println();
  setthr((uint16_t)uvSet, (uint16_t)uvClr, (uint16_t)ovSet, (uint16_t)ovClr);
}

void cellCheckConfig(char* buf)
{
  uint32_t duration;
  char* val = getNextToken(&buf);
  if (!val || !strlen(val)) {
    Serial.println("cellCheckConfig!");
    return;
  }
  duration = strtoul(val, (char **)NULL, 10);
  if (duration == ULONG_MAX || !duration) {
    Serial.println("cellCheckConfig vals!");
    return;
  }
  setDisplayFreqMs(duration);
}



/*
 * Command Processor
 * 
 */
void processADBmessages(char* adbBuf, uint16_t len)
{
  adbBuf[len] = '\0';
  adbBufP = adbBuf;
  char * cmd;
  Serial.print("processADBmessages: ");Serial.println(adbBuf);

  while (cmd = getADBcmd()) {
    processADBmessage(cmd);
  }
}

char* getADBcmd()
{
  if (!adbBufP || !*adbBufP) return 0;

  char* ret;
  while (true) {
    ret=strsep(&adbBufP,ADB_CMD_SEPS);
    if (*ret) return ret;
    if (!adbBufP || !*adbBufP) {
	return 0;
    }
  }
}

void processADBmessage(char* cmd)
{
  if (!cmd) {
    Serial.println("ADB cmd!");
    return;
  }
  Serial.print("Got ADB cmd: "); Serial.println(cmd);

  char* token = getNextToken(&cmd);
  if (!strcmp(token,"<pc>")) {
    pinConfig(cmd);
  } else if (!strcmp(token,"<ph>")) {
    pinHigh(cmd);
  } else if (!strcmp(token,"<pl>")) {
    pinLow(cmd);
  } else if (!strcmp(token,"<bms>")) {
    bmsCmd(cmd,(uint16_t)strlen(cmd));
  } else {
    Serial.print("Unknown ADB cmd: "); Serial.println(token);
  }
}

/* BMS Commands 

   <init> 
    do the initialization.

   <tc> <UVset> <UVclear> <OVset> <OVclear> 
    set thresholds.

   <ccc> <duration>
    set period between voltage checking
*/
void bmsCmd(char *cmd, uint16_t len) 
{
  char* token = getNextToken(&cmd);
  if (!strcmp(token,"init")) {
    doInit();
  }
  else if (!strcmp(token,"tc")) {
    thresholdConfig(cmd);
  } else if (!strcmp(token,"ccc")) {
    cellCheckConfig(cmd);
  } else {
    Serial.print("Unknown BMS cmd: "); Serial.println(token);
  } 
}

char* getNextToken(char** buf)
{
  if (!*buf || !**buf) return 0;

  char* ret;
  while (true) {
    ret=strsep(buf,ADB_TOKEN_SEPS);
    if (*ret) return ret;
    if (!*buf || !**buf) {
	return 0;
    }
  }
}

// Decode Pin Configuration, ie. analog/digital + pin number
bool decodePin(char* pinS, uint8_t* pin) 
{
  int l;
  if (!pinS || (l=strlen(pinS)) < 2 || l >3) {
    Serial.println("ADB cmd pin!");
    return false;
  }
  char *errS = 0;
  *pin = (uint8_t)strtol(pinS+1,&errS,10);
  if (errS && *errS) {
    Serial.println("ADB cmd pin2!");
    Serial.println(pinS);
    Serial.println(*errS,DEC);
    Serial.println(*pin,DEC);
    
    return false;
  }
    
  bool isDigital;
  if (pinS[0]=='d'||pinS[0]=='D') {
    isDigital = true;
  } else if (pinS[0]=='a'||pinS[0]=='A') {
    isDigital = false;
  } else {
    Serial.println("ADB cmd pin type!");
    return false;
  }

  // If analog pin, offset with the constant A0
  if (!isDigital) {
    *pin += A0;
  }
  return true;
}



